package xyz.xiezc.spider.control;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.Session;
import cn.hutool.setting.Setting;
import lombok.extern.slf4j.Slf4j;
import xyz.xiezc.ioc.annotation.Component;
import xyz.xiezc.ioc.annotation.InitMethod;
import xyz.xiezc.ioc.annotation.Inject;
import xyz.xiezc.spider.business.Business;
import xyz.xiezc.spider.common.BaseOrderInterface;
import xyz.xiezc.spider.common.Request;
import xyz.xiezc.spider.common.XBean;
import xyz.xiezc.spider.downloader.Downloader;
import xyz.xiezc.spider.duplicate.Duplicate;
import xyz.xiezc.spider.entity.MzituImgBean;
import xyz.xiezc.spider.prase.ParseResponse;
import xyz.xiezc.spider.statistic.Site;

import java.io.File;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Stream;

/**
 * @author wb-xzc291800
 * @date 2019/04/25 16:54
 */
@Slf4j
@Component
public class ScheduleCoreImpl implements ScheduleCore {

    private BlockingQueue<XBean> downLoadQueue = new LinkedBlockingQueue<>();
    private BlockingQueue<XBean> parseQueue = new LinkedBlockingQueue<>();
    private BlockingQueue<XBean> businessQueue = new LinkedBlockingQueue<>();
    private BlockingQueue<XBean> duplicteQueue = new LinkedBlockingQueue<>();


    @Inject(type = Downloader.class)
    List<Downloader> downloaders = new ArrayList<>();
    @Inject(type = ParseResponse.class)

    List<ParseResponse> parseResponses = new ArrayList<>();
    @Inject(type = Business.class)

    List<Business> businesses = new ArrayList<>();
    @Inject(type = Duplicate.class)
    Duplicate duplicate;


    @Inject
    Setting setting;

    @Inject
    Site site;


    @InitMethod
    public void init() {
        //校验
        if (downloaders.isEmpty()) {
            throw new RuntimeException("请注入下载器对象");
        }
        if (parseResponses.isEmpty()) {
            throw new RuntimeException("请注入解析器对象");
        }
        if (businesses.isEmpty()) {
            throw new RuntimeException("请注入业务逻辑对象");
        }
        //排序
        Collections.sort(downloaders, Comparator.comparingInt(BaseOrderInterface::orderNumber));
        Collections.sort(parseResponses, Comparator.comparingInt(BaseOrderInterface::orderNumber));
        Collections.sort(businesses, Comparator.comparingInt(BaseOrderInterface::orderNumber));
    }

    /**
     * 业务逻辑处理方法
     */
    @Override
    public void business() {
        XBean xBean = this.pullXBeanAsync(3);
        if (xBean != null) {
            for (Business business : businesses) {
                Integer maxErrorCount = setting.getInt("download.maxErrorCount");
                while (maxErrorCount > 0) {
                    maxErrorCount--;
                    try {
                        business.save(xBean);
                        xBean.setStatus(4);
                        this.pushXBean(xBean);
                        break;
                    } catch (SQLIntegrityConstraintViolationException e) {
                        log.error("违反唯一性约束, 说明数据库中已经有这条记录", e);
                        xBean.setStatus(4);
                        this.pushXBean(xBean);
                        break;
                    } catch (Exception e1) {
                        log.error("保存信息到数据库中出现异常, 重试指定次数后放入错误保存文件中", e1);
                        xBean.addErrMsg(e1.getMessage());
                    }
                }
            }
        }
    }

    /**
     * 去重处理方法
     */
    @Override
    public XBean repeat() {
        XBean xBeanRepeat = this.pullXBeanAsync(4);
        if (xBeanRepeat != null) {
            duplicate.isRepeat(xBeanRepeat);
            xBeanRepeat.getNextRequests().stream().forEach(request -> {
                this.pushXBean(new XBean(request, 1));
            });
        }
        return xBeanRepeat;
    }

    /**
     * 解析方法
     */
    @Override
    public void parse() {
        XBean xBean = this.pullXBeanAsync(2);
        if (xBean != null) {
            for (ParseResponse parseResponses : parseResponses) {
                try {
                    parseResponses.parse(xBean);
                    if (xBean.getStatus() == 3) {
                        this.pushXBean(xBean);
                        break;
                    }
                } catch (Exception e) {
                    log.error("解析出现异常, 放入错误XBean保存文件中, 不再重试了", e);
                    xBean.addErrMsg(e.getMessage());
                    site.addErrXBean(xBean);
                }
            }
        }
    }

    /**
     * 下载处理方法
     */
    @Override
    public void download() {
        for (Downloader downloader : downloaders) {
            XBean preDealXBean = this.pullXBeanAsync(1);
            if (preDealXBean != null) {
                try {

                    if (checkImgExist(preDealXBean)) {
                        continue;
                    }
                    ThreadUtil.sleep(setting.getInt("wait.time"));
                    XBean xBean = downloader.downloader(preDealXBean);
                    xBean.setStatus(2);
                    this.pushXBean(xBean);
                } catch (Exception e) {
                    log.error("下载出现异常", e);
                    preDealXBean.addErrMsg(e.getMessage());
                    int errCount = preDealXBean.getErrCount();
                    if (errCount > setting.getInt("download.maxErrorCount")) {
                        log.error("次链接下载异常次数太多, 将会记录文件中");
                        site.addErrXBean(preDealXBean);
                    } else {
                        log.error("将会重试............");
                        preDealXBean.setStatus(1);
                        preDealXBean.setResponse(null);
                        this.pushXBean(preDealXBean);
                    }
                }
            }
        }
    }

    /**
     * 判断图片是否已经存在数据库中了, 如果已经存在就不用重新下载了
     *
     * @param preDealXBean
     * @return
     */
    private boolean checkImgExist(XBean preDealXBean) {
        String url = preDealXBean.getRequest().getUrl();
        if (url.endsWith(".jpg")) {
            String path = URLUtil.getPath(url);
            String img = setting.getStr("data.dir");
            File file = FileUtil.file(img + path);
            Map<String, Object> extra = preDealXBean.getRequest().getExtra();
            Integer albumId = (Integer) extra.get("albumId");
            if (file.exists() && albumId != null) {
                log.info("已存在的图片, 不会下载了, url:{}, path:{}", url, path);
                MzituImgBean mzituImgBean = new MzituImgBean();
                mzituImgBean.setTitle(Objects.toString(extra.get("title")));
                mzituImgBean.setAlbumId(albumId);
                mzituImgBean.setFetchUrl(url);
                mzituImgBean.setPath(path);
                mzituImgBean.setSize((int) FileUtil.size(file));

                preDealXBean.setBean(CollUtil.newArrayList(mzituImgBean));
                preDealXBean.setStatus(3);
                //已经存在的图片
                preDealXBean.addOther("exist", true);
                this.pushXBean(preDealXBean);
                return true;
            }
        }
        return false;
    }

    @Override
    public void dealFirstImg() throws SQLException {
        Session session = Session.create();
        List<Entity> query = session.query("select * from xs_photo_album ");
        for (Entity entity : query) {
            Integer id = entity.getInt("id");
            List<Entity> entityList = session.query("select * from xs_photo " +
                    "where album_id =? " +
                    "order by `like` desc limit 1", id);
            if (entityList != null && !entityList.isEmpty()) {
                Entity entity1 = entityList.get(0);
                String path = entity1.getStr("path");
                String oldPath = entity.getStr("first_img");
                if (!Objects.equals(path, oldPath)) {
                    session.update(Entity.create()
                                    .set("first_img", path),
                            Entity.create().set("id", id));
                }
                log.info("id:{} set frist img , img path:{}", id, path);
            }
        }
        log.info("dealFirstImg 结束.......................");
    }

    @Override
    public void dealSeeAndLike() throws SQLException {
        Session session = Session.create();
        List<Entity> query = session.query("select sum(see) as see, sum(`like`) as `like`, album_id from xs_photo group by  album_id order by `like` desc");
        for (Entity entity : query) {
            Integer see = entity.getInt("see");
            Integer like = entity.getInt("like");
            if (see == like && see == 0) {
                continue;
            }
            Integer album_id = entity.getInt("album_id");
            Entity where = Entity.create("xs_photo_album").set("id", album_id);
            Entity set = Entity.create("xs_photo").set("see", see).set("like", like);
            session.update(set, where);
            log.info("dealSeeAndLike; see:{}, like:{}, id:{}", see, like, album_id);
        }
        log.info("dealSeeAndLike 结束..............");
    }

    /**
     * 获得队列的集合
     *
     * @param statu
     * @return
     */
    @Override
    public Stream<XBean> getQueueStream(int statu) {
        Stream<XBean> stringStream;
        switch (statu) {
            case 0:
            case 1:
                stringStream = downLoadQueue.stream();
                break;
            case 2:
                stringStream = parseQueue.stream();
                break;
            case 3:
                stringStream = businessQueue.stream();
                break;
            case 4:
                stringStream = duplicteQueue.stream();
                break;
            default:
                stringStream = null;
                break;
        }
        return stringStream;
    }

    /**
     * 推送到调度中心. 此方法是线程安全的
     *
     * @param xBean
     * @return
     */
    @Override
    public boolean pushXBean(XBean xBean) {
        site.addOne(xBean.getStatus());
        boolean flag = false;
        int status = xBean.getStatus();
        switch (status) {
            case 0:
            case 1:
                Request request = xBean.getRequest();
                Map<String, List<String>> header1 = request.getHeader();
                site.getCommonHeader().forEach((k, v) -> {
                    if (!header1.containsKey(k)) {
                        header1.put(k, CollUtil.newArrayList(v));
                    }
                });

                flag = downLoadQueue.offer(xBean);
                break;
            case 2:
                flag = parseQueue.offer(xBean);
                break;
            case 3:
                flag = businessQueue.offer(xBean);
                break;
            case 4:
                flag = duplicteQueue.offer(xBean);
                break;
            default:
                break;
        }
        return flag;
    }

    @Override
    public XBean pullXBeanAsync(int statu) {
        XBean xBean = null;
        switch (statu) {
            case 0:
            case 1:
                xBean = downLoadQueue.poll();
                break;
            case 2:
                xBean = parseQueue.poll();
                break;
            case 3:
                xBean = businessQueue.poll();
                break;
            case 4:
                xBean = duplicteQueue.poll();
                break;
            default:
                break;
        }
        //统计数据
        if (xBean != null) {
            site.deleteOne(statu);
        }
        return xBean;
    }

}
